package com.ruoyi.business.service.impl;

import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Arrays;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.business.mapper.TestSessionMapper;
import com.ruoyi.business.domain.TestSession;
import com.ruoyi.business.service.ITestSessionService;
import com.ruoyi.business.domain.vo.TestSessionStudentScoreExportVO;
import com.ruoyi.business.domain.Score;
import com.ruoyi.business.domain.User;
import com.ruoyi.business.domain.ClassInfo;
import com.ruoyi.business.mapper.ScoreMapper;
import com.ruoyi.business.mapper.UserMapper;
import com.ruoyi.business.mapper.ClassInfoMapper;
import com.ruoyi.business.domain.enums.ProjectTypeEnum;
import com.ruoyi.business.domain.vo.BmiClassStatExportVO;
import com.ruoyi.business.domain.vo.ClassProjectStatExportVO;
import com.ruoyi.business.domain.vo.ClassTotalScoreStatExportVO;
import com.ruoyi.business.domain.vo.SchoolTestReportVO;
import com.ruoyi.business.utils.ChartImgUtil;

/**
 * 测试场次Service业务层处理
 * 
 * @author lcz
 * @date 2025-02-20
 */
@Service
public class TestSessionServiceImpl implements ITestSessionService 
{
    @Autowired
    private TestSessionMapper testSessionMapper;
    @Autowired
    private ScoreMapper scoreMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private ClassInfoMapper classInfoMapper;

    /**
     * 查询测试场次
     * 
     * @param id 测试场次主键
     * @return 测试场次
     */
    @Override
    public TestSession selectTestSessionById(Long id)
    {
        return testSessionMapper.selectTestSessionById(id);
    }

    /**
     * 查询测试场次列表
     * 
     * @param testSession 测试场次
     * @return 测试场次
     */
    @Override
    public List<TestSession> selectTestSessionList(TestSession testSession)
    {
        return testSessionMapper.selectTestSessionList(testSession);
    }

    @Override
    public List<TestSession> selectTestSessionListByName(TestSession testSession)
    {
        return testSessionMapper.selectTestSessionListByName(testSession);
    }

    /**
     * 新增测试场次
     * 
     * @param testSession 测试场次
     * @return 结果
     */
    @Override
    public int insertTestSession(TestSession testSession)
    {
        testSession.setCreateTime(DateUtils.getNowDate());
        return testSessionMapper.insertTestSession(testSession);
    }

    /**
     * 修改测试场次
     * 
     * @param testSession 测试场次
     * @return 结果
     */
    @Override
    public int updateTestSession(TestSession testSession)
    {
        testSession.setUpdateTime(DateUtils.getNowDate());
        return testSessionMapper.updateTestSession(testSession);
    }

    /**
     * 批量删除测试场次
     * 
     * @param ids 需要删除的测试场次主键
     * @return 结果
     */
    @Override
    public int deleteTestSessionByIds(Long[] ids)
    {
        return testSessionMapper.deleteTestSessionByIds(ids);
    }

    /**
     * 删除测试场次信息
     * 
     * @param id 测试场次主键
     * @return 结果
     */
    @Override
    public int deleteTestSessionById(Long id)
    {
        return testSessionMapper.deleteTestSessionById(id);
    }

    @Override
    public R<List<TestSession>> frontEndList() {
        Long schoolId = SecurityUtils.getLoginUser().getUser().getSchoolId();
        List<TestSession> testSessions = testSessionMapper.frontEndList(schoolId);
        return R.ok(testSessions);
    }

    @Override
    public R<List<TestSession>> frontStartList() {
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        List<TestSession> testSessions = testSessionMapper.frontStartList(userId);
        return R.ok(testSessions);
    }

    @Override
    public List<TestSessionStudentScoreExportVO> buildExportStudentScores(Long testSessionId) {
        List<Long> studentIds = scoreMapper.selectStudentIdsByTestSessionId(testSessionId);
        List<TestSessionStudentScoreExportVO> exportList = new ArrayList<>();
        DecimalFormat df = new DecimalFormat("#.##");
        // 枚举所有科目（身高体重、肺活量、坐位体前屈、50米、仰卧起坐、立定跳远、跳绳、100米、200米、400米、800米、1000米、1500米、3000米）
        ProjectTypeEnum[] allProjects = {
            ProjectTypeEnum.HEIGHT, ProjectTypeEnum.WEIGHT, ProjectTypeEnum.LUNG_CAPACITY, ProjectTypeEnum.SIT_AND_REACH,
            ProjectTypeEnum.FIFTY_METER_DASH, ProjectTypeEnum.SIT_UPS, ProjectTypeEnum.STANDING_LONG_JUMP, ProjectTypeEnum.SKIP_ROPE,
            ProjectTypeEnum.RUN_100, ProjectTypeEnum.RUN_200, ProjectTypeEnum.RUN_400, ProjectTypeEnum.RUN_800, ProjectTypeEnum.RUN_1000,
            ProjectTypeEnum.RUN_1500, ProjectTypeEnum.RUN_3000
        };
        for (Long studentId : studentIds) {
            User student = userMapper.selectUserById(studentId);
            if (student == null) continue;
            TestSessionStudentScoreExportVO vo = new TestSessionStudentScoreExportVO();
            // 基本信息
            vo.setStudentName(student.getUserName());
            vo.setGender(student.getGender());
            vo.setStudentNo(student.getStudentNumber());
            vo.setNationCode("");
            vo.setBirthDate("");
            vo.setAddress("");
            vo.setGradeNo(student.getGrade());
            vo.setClassNo(student.getClassId() != null ? student.getClassId().toString() : "");
            vo.setClassName(student.getClassName());

            List<Score> scores = scoreMapper.selectScoresByTestSessionAndStudent(testSessionId, studentId);
            Map<String, Score> projectMaxScoreMap = new HashMap<>();
            for (Score s : scores) {
                String projectType = s.getProjectType();
                if (!projectMaxScoreMap.containsKey(projectType) ||
                    (s.getScore() != null && (projectMaxScoreMap.get(projectType).getScore() == null || s.getScore().compareTo(projectMaxScoreMap.get(projectType).getScore()) > 0))) {
                    projectMaxScoreMap.put(projectType, s);
                }
            }
            // 身高、体重
            Score heightScore = projectMaxScoreMap.get(ProjectTypeEnum.HEIGHT.getCode());
            vo.setHeight(heightScore != null && heightScore.getHeight() != null ? df.format(heightScore.getHeight().stripTrailingZeros()) : "");
            Score weightScore = projectMaxScoreMap.get(ProjectTypeEnum.WEIGHT.getCode());
            vo.setWeight(weightScore != null && weightScore.getWeight() != null ? df.format(weightScore.getWeight().stripTrailingZeros()) : "");
            // 其它项目赋值（保留两位小数，无小数不补0）
            Score bmi = heightScore; // BMI评分与身高体重同分组
            vo.setBmiScore(bmi != null && bmi.getSingleScore() != null ? df.format(new BigDecimal(bmi.getSingleScore())) : "");
            vo.setBmiLevel(bmi != null ? bmi.getGradeName() : "");
            Score vitalCapacity = projectMaxScoreMap.get(ProjectTypeEnum.LUNG_CAPACITY.getCode());
            vo.setVitalCapacity(vitalCapacity != null && vitalCapacity.getScore() != null ? df.format(vitalCapacity.getScore().stripTrailingZeros()) : "");
            vo.setVitalCapacityScore(vitalCapacity != null && vitalCapacity.getSingleScore() != null ? df.format(new BigDecimal(vitalCapacity.getSingleScore())) : "");
            vo.setVitalCapacityLevel(vitalCapacity != null ? vitalCapacity.getGradeName() : "");
            Score sitAndReach = projectMaxScoreMap.get(ProjectTypeEnum.SIT_AND_REACH.getCode());
            vo.setSitAndReach(sitAndReach != null && sitAndReach.getScore() != null ? df.format(sitAndReach.getScore().stripTrailingZeros()) : "");
            vo.setSitAndReachScore(sitAndReach != null && sitAndReach.getSingleScore() != null ? df.format(new BigDecimal(sitAndReach.getSingleScore())) : "");
            vo.setSitAndReachLevel(sitAndReach != null ? sitAndReach.getGradeName() : "");
            Score run50m = projectMaxScoreMap.get(ProjectTypeEnum.FIFTY_METER_DASH.getCode());
            vo.setRun50m(run50m != null && run50m.getScore() != null ? df.format(run50m.getScore().stripTrailingZeros()) : "");
            vo.setRun50mScore(run50m != null && run50m.getSingleScore() != null ? df.format(new BigDecimal(run50m.getSingleScore())) : "");
            vo.setRun50mLevel(run50m != null ? run50m.getGradeName() : "");
            Score sitUp = projectMaxScoreMap.get(ProjectTypeEnum.SIT_UPS.getCode());
            vo.setSitUp(sitUp != null && sitUp.getScore() != null ? df.format(sitUp.getScore().stripTrailingZeros()) : "");
            vo.setSitUpScore(sitUp != null && sitUp.getSingleScore() != null ? df.format(new BigDecimal(sitUp.getSingleScore())) : "");
            vo.setSitUpLevel(sitUp != null ? sitUp.getGradeName() : "");
            Score standingLongJump = projectMaxScoreMap.get(ProjectTypeEnum.STANDING_LONG_JUMP.getCode());
            vo.setStandingLongJump(standingLongJump != null && standingLongJump.getScore() != null ? df.format(standingLongJump.getScore().stripTrailingZeros()) : "");
            vo.setStandingLongJumpScore(standingLongJump != null && standingLongJump.getSingleScore() != null ? df.format(new BigDecimal(standingLongJump.getSingleScore())) : "");
            vo.setStandingLongJumpLevel(standingLongJump != null ? standingLongJump.getGradeName() : "");
            Score ropeSkipping = projectMaxScoreMap.get(ProjectTypeEnum.SKIP_ROPE.getCode());
            vo.setRopeSkipping(ropeSkipping != null && ropeSkipping.getScore() != null ? df.format(ropeSkipping.getScore().stripTrailingZeros()) : "");
            vo.setRopeSkippingScore(ropeSkipping != null && ropeSkipping.getSingleScore() != null ? df.format(new BigDecimal(ropeSkipping.getSingleScore())) : "");
            vo.setRopeSkippingLevel(ropeSkipping != null ? ropeSkipping.getGradeName() : "");
            Score run100m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_100.getCode());
            vo.setRun100m(run100m != null && run100m.getScore() != null ? df.format(run100m.getScore().stripTrailingZeros()) : "");
            vo.setRun100mScore(run100m != null && run100m.getSingleScore() != null ? df.format(new BigDecimal(run100m.getSingleScore())) : "");
            vo.setRun100mLevel(run100m != null ? run100m.getGradeName() : "");
            Score run200m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_200.getCode());
            vo.setRun200m(run200m != null && run200m.getScore() != null ? df.format(run200m.getScore().stripTrailingZeros()) : "");
            vo.setRun200mScore(run200m != null && run200m.getSingleScore() != null ? df.format(new BigDecimal(run200m.getSingleScore())) : "");
            vo.setRun200mLevel(run200m != null ? run200m.getGradeName() : "");
            Score run400m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_400.getCode());
            vo.setRun400m(run400m != null && run400m.getScore() != null ? df.format(run400m.getScore().stripTrailingZeros()) : "");
            vo.setRun400mScore(run400m != null && run400m.getSingleScore() != null ? df.format(new BigDecimal(run400m.getSingleScore())) : "");
            vo.setRun400mLevel(run400m != null ? run400m.getGradeName() : "");
            Score run800m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_800.getCode());
            vo.setRun800m(run800m != null && run800m.getScore() != null ? df.format(run800m.getScore().stripTrailingZeros()) : "");
            vo.setRun800mScore(run800m != null && run800m.getSingleScore() != null ? df.format(new BigDecimal(run800m.getSingleScore())) : "");
            vo.setRun800mLevel(run800m != null ? run800m.getGradeName() : "");
            Score run1000m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_1000.getCode());
            vo.setRun1000m(run1000m != null && run1000m.getScore() != null ? df.format(run1000m.getScore().stripTrailingZeros()) : "");
            vo.setRun1000mScore(run1000m != null && run1000m.getSingleScore() != null ? df.format(new BigDecimal(run1000m.getSingleScore())) : "");
            vo.setRun1000mLevel(run1000m != null ? run1000m.getGradeName() : "");
            Score run1500m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_1500.getCode());
            vo.setRun1500m(run1500m != null && run1500m.getScore() != null ? df.format(run1500m.getScore().stripTrailingZeros()) : "");
            vo.setRun1500mScore(run1500m != null && run1500m.getSingleScore() != null ? df.format(new BigDecimal(run1500m.getSingleScore())) : "");
            vo.setRun1500mLevel(run1500m != null ? run1500m.getGradeName() : "");
            Score run3000m = projectMaxScoreMap.get(ProjectTypeEnum.RUN_3000.getCode());
            vo.setRun3000m(run3000m != null && run3000m.getScore() != null ? df.format(run3000m.getScore().stripTrailingZeros()) : "");
            vo.setRun3000mScore(run3000m != null && run3000m.getSingleScore() != null ? df.format(new BigDecimal(run3000m.getSingleScore())) : "");
            vo.setRun3000mLevel(run3000m != null ? run3000m.getGradeName() : "");

            // 计算总分（所有科目都计入，无成绩按0分）
            double total = 0;
            int totalCount = 0;
            for (ProjectTypeEnum p : allProjects) {
                Score sc = projectMaxScoreMap.get(p.getCode());
                double single = 0;
                if (sc != null && sc.getSingleScore() != null) {
                    single = sc.getSingleScore();
                }
                total += single;
                totalCount++;
            }
            double avg = totalCount > 0 ? total / totalCount : 0;
            String avgStr = df.format(new BigDecimal(avg).stripTrailingZeros());
            vo.setTotalScore(avgStr);
            // 总分等级
            double avgVal = 0;
            try { avgVal = Double.parseDouble(avgStr); } catch (Exception ignore) {}
            if (avgVal < 60) {
                vo.setTotalLevel("不及格");
            } else if (avgVal < 80) {
                vo.setTotalLevel("及格");
            } else if (avgVal < 90) {
                vo.setTotalLevel("良好");
            } else {
                vo.setTotalLevel("优秀");
            }
            exportList.add(vo);
        }
        return exportList;
    }

    @Override
    public List<BmiClassStatExportVO> buildBmiClassStatExport(Long testSessionId) {

        Long schoolId = SecurityUtils.getLoginUser().getUser().getSchoolId();

        // 1. 查询该场次所有学生成绩
        List<Long> studentIds = scoreMapper.selectStudentIdsByTestSessionId(testSessionId);
        List<Score> allScores = new ArrayList<>();
        for (Long studentId : studentIds) {
            allScores.addAll(scoreMapper.selectScoresByTestSessionAndStudent(testSessionId, studentId));
        }
        // 2. 按班级分组
        Map<String, List<Score>> classScoreMap = new HashMap<>();
        Map<String, String> classNameMap = new HashMap<>();
        Map<String, String> schoolNameMap = new HashMap<>();
        for (Score s : allScores) {
            if (!ProjectTypeEnum.HEIGHT.getCode().equals(s.getProjectType())) continue; // 只统计身高体重项目
            String classId = s.getClassId() != null ? s.getClassId().toString() : "";
            classScoreMap.computeIfAbsent(classId, k -> new ArrayList<>()).add(s);
            classNameMap.put(classId, s.getClassName());
            schoolNameMap.put(classId, s.getSchoolName());
        }
        // 3. 查询所有班级总人数（通过UserMapper统计）
        Map<String, Integer> classTotalMap = new HashMap<>();
        List<Map<String, Object>> classUserCounts = userMapper.countUserGroupByClassId();
        for (Map<String, Object> map : classUserCounts) {
            String classId = map.get("classId") != null ? map.get("classId").toString() : "";
            Integer count = map.get("count") != null ? Integer.parseInt(map.get("count").toString()) : 0;
            classTotalMap.put(classId, count);
        }
        // 4. 查询所有班级信息，确保所有班级都展示
        ClassInfo classInfoParam = new ClassInfo();
        classInfoParam.setSchoolId(schoolId);
        List<ClassInfo> allClasses = classInfoMapper.selectClassInfoList(classInfoParam);
        List<BmiClassStatExportVO> voList = new ArrayList<>();
        for (ClassInfo c : allClasses) {
            String classId = c.getId() != null ? c.getId().toString() : "";
            List<Score> scores = classScoreMap.getOrDefault(classId, new ArrayList<>());
            int testedCount = scores.size();
            int totalCount = classTotalMap.getOrDefault(classId, 0);
            int untestedCount = totalCount - testedCount;
            String className = c.getClassName();
            String schoolName = c.getSchoolName();
            // 统计BMI成绩、得分、等级
            List<Double> bmiList = new ArrayList<>();
            List<Double> scoreList = new ArrayList<>();
            int normal = 0, overweight = 0, underweight = 0, obese = 0;
            for (Score s : scores) {
                if (s.getHeight() != null && s.getWeight() != null && s.getHeight().doubleValue() > 0) {
                    double bmi = s.getWeight().doubleValue() / (s.getHeight().doubleValue() * s.getHeight().doubleValue());
                    bmiList.add(bmi);
                }
                if (s.getSingleScore() != null) {
                    scoreList.add(s.getSingleScore().doubleValue());
                }
                if (s.getGradeName() != null) {
                    switch (s.getGradeName()) {
                        case "正常": normal++; break;
                        case "超重": overweight++; break;
                        case "低体重": underweight++; break;
                        case "肥胖": obese++; break;
                    }
                }
            }
            // 平均值、标准差
            String avgBmiWithStd = calcAvgStd(bmiList);
            String avgScoreWithStd = calcAvgStd(scoreList);
            // 比例
            String completionRate = formatPercent(testedCount, totalCount);
            String normalRate = formatPercent(normal, testedCount);
            String overweightRate = formatPercent(overweight, testedCount);
            String underweightRate = formatPercent(underweight, testedCount);
            String obeseRate = formatPercent(obese, testedCount);
            // VO
            BmiClassStatExportVO vo = new BmiClassStatExportVO();
            vo.setSchool(schoolName + "(" + getTestSessionName(testSessionId) + ")");
            vo.setClassName(className);
            vo.setTotalCount(totalCount);
            vo.setTestedCount(testedCount);
            vo.setCompletionRate(completionRate);
            vo.setUntestedCount(untestedCount);
            vo.setAvgBmiWithStd(avgBmiWithStd);
            vo.setAvgScoreWithStd(avgScoreWithStd);
            vo.setNormalCount(normal);
            vo.setNormalRate(normalRate);
            vo.setOverweightCount(overweight);
            vo.setOverweightRate(overweightRate);
            vo.setUnderweightCount(underweight);
            vo.setUnderweightRate(underweightRate);
            vo.setObeseCount(obese);
            vo.setObeseRate(obeseRate);
            voList.add(vo);
        }
        return voList;
    }

    // 计算平均值±标准差，保留两位小数
    private String calcAvgStd(List<Double> list) {
        if (list == null || list.isEmpty()) return "";
        double avg = list.stream().mapToDouble(Double::doubleValue).average().orElse(0);
        double std = Math.sqrt(list.stream().mapToDouble(x -> Math.pow(x - avg, 2)).average().orElse(0));
        return String.format("%.2f±%.2f", avg, std);
    }
    // 百分比格式化
    private String formatPercent(int num, int total) {
        if (total == 0) return "0.00%";
        double percent = num * 100.0 / total;
        return String.format("%.2f%%", percent);
    }
    // 获取测试名称
    private String getTestSessionName(Long testSessionId) {
        TestSession ts = testSessionMapper.selectTestSessionById(testSessionId);
        return ts != null ? ts.getTestName() : "";
    }

    @Override
    public List<ClassProjectStatExportVO> buildClassProjectStatExport(Long testSessionId) {
        Long schoolId = SecurityUtils.getLoginUser().getUser().getSchoolId();
        // 查询所有班级
        ClassInfo classInfoParam = new ClassInfo();
        classInfoParam.setSchoolId(schoolId);
        List<ClassInfo> allClasses = classInfoMapper.selectClassInfoList(classInfoParam);
        // 查询所有项目
        ProjectTypeEnum[] allProjects = ProjectTypeEnum.values();
        // 一次性查出所有成绩
        List<Score> allScores = scoreMapper.selectScoresByTestSessionId(testSessionId);
        // 预分组：Map<班级ID, Map<项目类型, List<Score>>>
        Map<String, Map<String, List<Score>>> classProjectScoreMap = new HashMap<>();
        for (Score s : allScores) {
            String classId = s.getClassId() != null ? s.getClassId().toString() : "";
            String projectType = s.getProjectType();
            classProjectScoreMap
                .computeIfAbsent(classId, k -> new HashMap<>())
                .computeIfAbsent(projectType, k -> new ArrayList<>())
                .add(s);
        }
        // 查询所有班级总人数
        Map<String, Integer> classTotalMap = new HashMap<>();
        List<Map<String, Object>> classUserCounts = userMapper.countUserGroupByClassId();
        for (Map<String, Object> map : classUserCounts) {
            String classId = map.get("classId") != null ? map.get("classId").toString() : "";
            Integer count = map.get("count") != null ? Integer.parseInt(map.get("count").toString()) : 0;
            classTotalMap.put(classId, count);
        }
        List<ClassProjectStatExportVO> voList = new ArrayList<>();
        for (ClassInfo c : allClasses) {
            String classId = c.getId() != null ? c.getId().toString() : "";
            String className = c.getClassName();
            String schoolName = c.getSchoolName();
            String gradeNo = c.getGrade();
            String gradeName = c.getGradename();
            int totalCount = classTotalMap.getOrDefault(classId, 0);
            Map<String, List<Score>> projectScoreMap = classProjectScoreMap.getOrDefault(classId, Collections.emptyMap());
            for (ProjectTypeEnum project : allProjects) {
                List<Score> scores = projectScoreMap.getOrDefault(project.getCode(), Collections.emptyList());
                int testedCount = scores.size();
                int untestedCount = totalCount - testedCount;
                // 成绩统计
                List<Double> scoreList = new ArrayList<>();
                List<Double> singleScoreList = new ArrayList<>();
                for (Score s : scores) {
                    if (s.getScore() != null) scoreList.add(s.getScore().doubleValue());
                    if (s.getSingleScore() != null) singleScoreList.add(s.getSingleScore().doubleValue());
                }
                String avgScore = calcAvg(scoreList);
                String stdScore = calcStd(scoreList);
                String avgSingleScore = calcAvg(singleScoreList);
                String maxScore = scoreList.isEmpty() ? "" : formatDouble(scoreList.stream().max(Double::compareTo).orElse(0.0));
                String minScore = scoreList.isEmpty() ? "" : formatDouble(scoreList.stream().min(Double::compareTo).orElse(0.0));
                String medianScore = calcMedian(scoreList);
                // 等级统计
                int excellent = 0, fine = 0, pass = 0, fail = 0;
                for (Score s : scores) {
                    if (s.getGradeName() == null) continue;
                    switch (s.getGradeName()) {
                        case "优秀": excellent++; break;
                        case "良好": fine++; break;
                        case "及格": pass++; break;
                        case "不及格": fail++; break;
                    }
                }
                int good = excellent + fine;
                // 比例
                String completionRate = formatPercent(testedCount, totalCount);
                String goodRate = formatPercent(good, totalCount);
                String passRate = formatPercent(pass, totalCount);
                String excellentRate = formatPercent(excellent, totalCount);
                String fineRate = formatPercent(fine, totalCount);
                String failRate = formatPercent(fail, totalCount);
                // VO
                ClassProjectStatExportVO vo = new ClassProjectStatExportVO();
                vo.setSchool(schoolName + "(" + getTestSessionName(testSessionId) + ")");
                vo.setProjectName(project.getValue());
                vo.setGradeNo(gradeNo);
                vo.setGradeName(gradeName);
                vo.setClassName(className);
                vo.setTotalCount(totalCount);
                vo.setTestedCount(testedCount);
                vo.setCompletionRate(completionRate);
                vo.setUntestedCount(untestedCount);
                vo.setAvgScore(avgScore);
                vo.setStdScore(stdScore);
                vo.setAvgSingleScore(avgSingleScore);
                vo.setMaxScore(maxScore);
                vo.setMinScore(minScore);
                vo.setMedianScore(medianScore);
                vo.setGoodCount(good);
                vo.setGoodRate(goodRate);
                vo.setPassCount(pass);
                vo.setPassRate(passRate);
                vo.setExcellentCount(excellent);
                vo.setExcellentRate(excellentRate);
                vo.setFineCount(fine);
                vo.setFineRate(fineRate);
                vo.setFailCount(fail);
                vo.setFailRate(failRate);
                voList.add(vo);
            }
        }
        return voList;
    }

    // 平均值，保留两位小数
    private String calcAvg(List<Double> list) {
        if (list == null || list.isEmpty()) return "";
        double avg = list.stream().mapToDouble(Double::doubleValue).average().orElse(0);
        return formatDouble(avg);
    }
    // 标准差，保留两位小数
    private String calcStd(List<Double> list) {
        if (list == null || list.isEmpty()) return "";
        double avg = list.stream().mapToDouble(Double::doubleValue).average().orElse(0);
        double std = Math.sqrt(list.stream().mapToDouble(x -> Math.pow(x - avg, 2)).average().orElse(0));
        return formatDouble(std);
    }
    // 中位数，保留两位小数
    private String calcMedian(List<Double> list) {
        if (list == null || list.isEmpty()) return "";
        List<Double> sorted = new ArrayList<>(list);
        sorted.sort(Double::compareTo);
        int n = sorted.size();
        double median = n % 2 == 1 ? sorted.get(n/2) : (sorted.get(n/2-1) + sorted.get(n/2)) / 2.0;
        return formatDouble(median);
    }
    // double保留两位小数，无小数不补0
    private String formatDouble(double d) {
        if (d == (long)d) return String.valueOf((long)d);
        return String.format("%.2f", d);
    }

    @Override
    public List<ClassTotalScoreStatExportVO> buildClassTotalScoreStatExport(Long testSessionId) {
        Long schoolId = SecurityUtils.getLoginUser().getUser().getSchoolId();
        // 查询所有班级
        ClassInfo classInfoParam = new ClassInfo();
        classInfoParam.setSchoolId(schoolId);
        List<ClassInfo> allClasses = classInfoMapper.selectClassInfoList(classInfoParam);
        // 查询所有项目
        ProjectTypeEnum[] allProjects = ProjectTypeEnum.values();
        // 一次性查出所有成绩
        List<Score> allScores = scoreMapper.selectScoresByTestSessionId(testSessionId);
        // 查询所有班级总人数
        Map<String, Integer> classTotalMap = new HashMap<>();
        List<Map<String, Object>> classUserCounts = userMapper.countUserGroupByClassId();
        for (Map<String, Object> map : classUserCounts) {
            String classId = map.get("classId") != null ? map.get("classId").toString() : "";
            Integer count = map.get("count") != null ? Integer.parseInt(map.get("count").toString()) : 0;
            classTotalMap.put(classId, count);
        }
        // 预分组：Map<班级ID, Map<学生ID, List<Score>>>
        Map<String, Map<Long, List<Score>>> classStudentScoreMap = new HashMap<>();
        for (Score s : allScores) {
            String classId = s.getClassId() != null ? s.getClassId().toString() : "";
            Long studentId = s.getStudentId();
            classStudentScoreMap
                .computeIfAbsent(classId, k -> new HashMap<>())
                .computeIfAbsent(studentId, k -> new ArrayList<>())
                .add(s);
        }
        List<ClassTotalScoreStatExportVO> voList = new ArrayList<>();
        for (ClassInfo c : allClasses) {
            String classId = c.getId() != null ? c.getId().toString() : "";
            String className = c.getClassName();
            String schoolName = c.getSchoolName();
            String gradeNo = c.getGrade();
            String gradeName = c.getGradename();
            int totalCount = classTotalMap.getOrDefault(classId, 0);
            Map<Long, List<Score>> studentScoreMap = classStudentScoreMap.getOrDefault(classId, Collections.emptyMap());
            int completedCount = 0, uncompletedCount = 0;
            List<Double> totalScoreList = new ArrayList<>();
            int good = 0, pass = 0, fail = 0;
            for (Map.Entry<Long, List<Score>> entry : studentScoreMap.entrySet()) {
                List<Score> scores = entry.getValue();
                // 每个学生每个项目取最高分
                Map<String, Double> projectScoreMap = new HashMap<>();
                for (ProjectTypeEnum p : allProjects) {
                    double max = 0;
                    for (Score s : scores) {
                        if (p.getCode().equals(s.getProjectType()) && s.getSingleScore() != null) {
                            max = Math.max(max, s.getSingleScore());
                        }
                    }
                    projectScoreMap.put(p.getCode(), max);
                }
                // 判断是否所有项目都有成绩
                boolean completed = true;
                double sum = 0;
                int count = 0;
                for (ProjectTypeEnum p : allProjects) {
                    double v = projectScoreMap.getOrDefault(p.getCode(), 0.0);
                    sum += v;
                    count++;
                    if (v == 0) completed = false;
                }
                double avg = count > 0 ? sum / count : 0;
                totalScoreList.add(avg);
                if (completed) completedCount++; else uncompletedCount++;
                // 总分等级
                if (avg < 60) fail++;
                else if (avg < 80) pass++;
                else if (avg < 90) good++;
                else good++;
            }
            // 补全未测试学生（无成绩，全部为0）
            int missing = totalCount - studentScoreMap.size();
            for (int i = 0; i < missing; i++) {
                totalScoreList.add(0.0);
                uncompletedCount++;
                fail++;
            }
            // 平均分±标准差
            String avgScoreWithStd = calcAvgStd(totalScoreList);
            // 比例
            String completionRate = formatPercent(completedCount, totalCount);
            String goodRate = formatPercent(good, totalCount);
            String passRate = formatPercent(pass, totalCount);
            String failRate = formatPercent(fail, totalCount);
            // VO
            ClassTotalScoreStatExportVO vo = new ClassTotalScoreStatExportVO();
            vo.setSchool(schoolName + "(" + getTestSessionName(testSessionId) + ")");
            vo.setGradeNo(gradeNo);
            vo.setGradeName(gradeName);
            vo.setClassName(className);
            vo.setTotalCount(totalCount);
            vo.setUncompletedCount(uncompletedCount);
            vo.setCompletedCount(completedCount);
            vo.setCompletionRate(completionRate);
            vo.setAvgScoreWithStd(avgScoreWithStd);
            vo.setGoodCount(good);
            vo.setGoodRate(goodRate);
            vo.setPassCount(pass);
            vo.setPassRate(passRate);
            vo.setFailCount(fail);
            vo.setFailRate(failRate);
            voList.add(vo);
        }
        return voList;
    }

    @Override
    public SchoolTestReportVO buildSchoolTestReport(Long testSessionId) {
        Long schoolId = SecurityUtils.getLoginUser().getUser().getSchoolId();
        // 查询学校、测试名称
        TestSession testSession = testSessionMapper.selectTestSessionById(testSessionId);
        String schoolName = testSession != null ? testSession.getSchoolName() : "";
        String testName = testSession != null ? testSession.getTestName() : "";
        // 查询所有学生
        List<Map<String, Object>> classUserCounts = userMapper.countUserGroupByClassId();
        List<User> allUsers = userMapper.selectUserListBySchoolId(schoolId);
        int maleCount = 0, femaleCount = 0;
        for (User u : allUsers) {
            if ("0".equals(u.getType())) { // 只统计学生
                if ("1".equals(u.getGender())) maleCount++;
                else if ("2".equals(u.getGender())) femaleCount++;
            }
        }
        int totalCount = maleCount + femaleCount;
        double malePercent = totalCount > 0 ? maleCount * 100.0 / totalCount : 0;
        double femalePercent = totalCount > 0 ? femaleCount * 100.0 / totalCount : 0;
        // 查询所有项目
        ProjectTypeEnum[] allProjects = ProjectTypeEnum.values();
        // 一次性查出所有成绩
        List<Score> allScores = scoreMapper.selectScoresByTestSessionId(testSessionId);
        // 预分组：Map<学生ID, List<Score>>
        Map<Long, List<Score>> studentScoreMap = new HashMap<>();
        for (Score s : allScores) {
            Long studentId = s.getStudentId();
            studentScoreMap.computeIfAbsent(studentId, k -> new ArrayList<>()).add(s);
        }
        // 统计完成情况
        int allCompletedCount = 0, notAllCompletedCount = 0, notTestedCount = 0;
        int maleAllCompletedCount = 0, maleNotAllCompletedCount = 0, maleNotTestedCount = 0;
        int femaleAllCompletedCount = 0, femaleNotAllCompletedCount = 0, femaleNotTestedCount = 0;
        for (User u : allUsers) {
            List<Score> scores = studentScoreMap.getOrDefault(u.getId(), Collections.emptyList());
            // 每个项目取最高分
            Map<String, Double> projectScoreMap = new HashMap<>();
            for (ProjectTypeEnum p : allProjects) {
                double max = 0;
                for (Score s : scores) {
                    if (p.getCode().equals(s.getProjectType()) && s.getSingleScore() != null) {
                        max = Math.max(max, s.getSingleScore());
                    }
                }
                projectScoreMap.put(p.getCode(), max);
            }
            int projectCount = allProjects.length;
            int hasScoreCount = 0;
            for (ProjectTypeEnum p : allProjects) {
                if (projectScoreMap.getOrDefault(p.getCode(), 0.0) > 0) hasScoreCount++;
            }
            boolean allCompleted = hasScoreCount == projectCount;
            boolean notTested = hasScoreCount == 0;
            if (allCompleted) allCompletedCount++;
            else if (notTested) notTestedCount++;
            else notAllCompletedCount++;
            if ("1".equals(u.getGender())) {
                if (allCompleted) maleAllCompletedCount++;
                else if (notTested) maleNotTestedCount++;
                else maleNotAllCompletedCount++;
            } else if ("2".equals(u.getGender())) {
                if (allCompleted) femaleAllCompletedCount++;
                else if (notTested) femaleNotTestedCount++;
                else femaleNotAllCompletedCount++;
            }
        }
        int maleTotalCount = maleAllCompletedCount + maleNotAllCompletedCount + maleNotTestedCount;
        int femaleTotalCount = femaleAllCompletedCount + femaleNotAllCompletedCount + femaleNotTestedCount;
        // 组装表格数据
        List<List<String>> completionTableRows = new ArrayList<>();
        completionTableRows.add(Arrays.asList("测试情况", "全体(人数/比例)", "男生(人数/比例)", "女生(人数/比例)"));
        completionTableRows.add(Arrays.asList(
            "全部完成",
            allCompletedCount + "(" + formatPercent(allCompletedCount, totalCount) + ")",
            maleAllCompletedCount + "(" + formatPercent(maleAllCompletedCount, totalCount) + ")",
            femaleAllCompletedCount + "(" + formatPercent(femaleAllCompletedCount, totalCount) + ")"
        ));
        completionTableRows.add(Arrays.asList(
            "未全部完成",
            notAllCompletedCount + "(" + formatPercent(notAllCompletedCount, totalCount) + ")",
            maleNotAllCompletedCount + "(" + formatPercent(maleNotAllCompletedCount, totalCount) + ")",
            femaleNotAllCompletedCount + "(" + formatPercent(femaleNotAllCompletedCount, totalCount) + ")"
        ));
        completionTableRows.add(Arrays.asList(
            "未测试",
            notTestedCount + "(" + formatPercent(notTestedCount, totalCount) + ")",
            maleNotTestedCount + "(" + formatPercent(maleNotTestedCount, totalCount) + ")",
            femaleNotTestedCount + "(" + formatPercent(femaleNotTestedCount, totalCount) + ")"
        ));
        // 组装VO
        SchoolTestReportVO vo = new SchoolTestReportVO();
        vo.setSchoolName(schoolName);
        vo.setTestName(testName);
        vo.setMaleCount(maleCount);
        vo.setFemaleCount(femaleCount);
        vo.setMalePercent(malePercent);
        vo.setFemalePercent(femalePercent);
        vo.setAllCompletedCount(allCompletedCount);
        vo.setNotAllCompletedCount(notAllCompletedCount);
        vo.setNotTestedCount(notTestedCount);
        vo.setTotalCount(totalCount);
        vo.setMaleAllCompletedCount(maleAllCompletedCount);
        vo.setMaleNotAllCompletedCount(maleNotAllCompletedCount);
        vo.setMaleNotTestedCount(maleNotTestedCount);
        vo.setMaleTotalCount(maleTotalCount);
        vo.setFemaleAllCompletedCount(femaleAllCompletedCount);
        vo.setFemaleNotAllCompletedCount(femaleNotAllCompletedCount);
        vo.setFemaleNotTestedCount(femaleNotTestedCount);
        vo.setFemaleTotalCount(femaleTotalCount);
        vo.setCompletionTableRows(completionTableRows);
        // 统计全校学生总分评级分布
        int excellent = 0, fine = 0, pass = 0, fail = 0;
        int maleExcellent = 0, maleFine = 0, malePass = 0, maleFail = 0;
        int femaleExcellent = 0, femaleFine = 0, femalePass = 0, femaleFail = 0;
        for (User u : allUsers) {
            if (!"0".equals(u.getType())) continue; // 只统计学生
            List<Score> scores = studentScoreMap.getOrDefault(u.getId(), Collections.emptyList());
            Map<String, Double> projectScoreMap = new HashMap<>();
            for (ProjectTypeEnum p : allProjects) {
                double max = 0;
                for (Score s : scores) {
                    if (p.getCode().equals(s.getProjectType()) && s.getSingleScore() != null) {
                        max = Math.max(max, s.getSingleScore());
                    }
                }
                projectScoreMap.put(p.getCode(), max);
            }
            double sum = 0;
            int count = 0;
            for (ProjectTypeEnum p : allProjects) {
                sum += projectScoreMap.getOrDefault(p.getCode(), 0.0);
                count++;
            }
            double avg = count > 0 ? sum / count : 0;
            if (avg < 60) {
                fail++;
                if ("1".equals(u.getGender())) maleFail++;
                else if ("2".equals(u.getGender())) femaleFail++;
            } else if (avg < 80) {
                pass++;
                if ("1".equals(u.getGender())) malePass++;
                else if ("2".equals(u.getGender())) femalePass++;
            } else if (avg < 90) {
                fine++;
                if ("1".equals(u.getGender())) maleFine++;
                else if ("2".equals(u.getGender())) femaleFine++;
            } else {
                excellent++;
                if ("1".equals(u.getGender())) maleExcellent++;
                else if ("2".equals(u.getGender())) femaleExcellent++;
            }
        }
        int good = excellent + fine;
        int maleGood = maleExcellent + maleFine;
        int femaleGood = femaleExcellent + femaleFine;
        int qualified = good + pass;
        int maleQualified = maleGood + malePass;
        int femaleQualified = femaleGood + femalePass;
        List<List<String>> scoreLevelTableRows = new ArrayList<>();
        scoreLevelTableRows.add(Arrays.asList("测试情况", "全体(人数/比例)", "男生(人数/比例)", "女生(人数/比例)"));
        scoreLevelTableRows.add(Arrays.asList(
            "优秀",
            excellent + "(" + formatPercent(excellent, totalCount) + ")",
            maleExcellent + "(" + formatPercent(maleExcellent, totalCount) + ")",
            femaleExcellent + "(" + formatPercent(femaleExcellent, totalCount) + ")"
        ));
        scoreLevelTableRows.add(Arrays.asList(
            "良好",
            fine + "(" + formatPercent(fine, totalCount) + ")",
            maleFine + "(" + formatPercent(maleFine, totalCount) + ")",
            femaleFine + "(" + formatPercent(femaleFine, totalCount) + ")"
        ));
        scoreLevelTableRows.add(Arrays.asList(
            "优良合计",
            good + "(" + formatPercent(good, totalCount) + ")",
            maleGood + "(" + formatPercent(maleGood, totalCount) + ")",
            femaleGood + "(" + formatPercent(femaleGood, totalCount) + ")"
        ));
        scoreLevelTableRows.add(Arrays.asList(
            "及格",
            pass + "(" + formatPercent(pass, totalCount) + ")",
            malePass + "(" + formatPercent(malePass, totalCount) + ")",
            femalePass + "(" + formatPercent(femalePass, totalCount) + ")"
        ));
        scoreLevelTableRows.add(Arrays.asList(
            "合格合计",
            qualified + "(" + formatPercent(qualified, totalCount) + ")",
            maleQualified + "(" + formatPercent(maleQualified, totalCount) + ")",
            femaleQualified + "(" + formatPercent(femaleQualified, totalCount) + ")"
        ));
        scoreLevelTableRows.add(Arrays.asList(
            "不及格",
            fail + "(" + formatPercent(fail, totalCount) + ")",
            maleFail + "(" + formatPercent(maleFail, totalCount) + ")",
            femaleFail + "(" + formatPercent(femaleFail, totalCount) + ")"
        ));
        vo.setScoreLevelTableRows(scoreLevelTableRows);
        // 生成综合评分情况柱状图
        String[] categories = {"优秀", "良好", "优良合计", "及格", "合格合计", "不及格"};
        int[] allValues = {excellent, fine, good, pass, qualified, fail};
        int[] maleValues = {maleExcellent, maleFine, maleGood, malePass, maleQualified, maleFail};
        int[] femaleValues = {femaleExcellent, femaleFine, femaleGood, femalePass, femaleQualified, femaleFail};
        try {
            vo.setScoreLevelBarAllImg(ChartImgUtil.createBarChart("综合评分情况(全体)", categories, allValues));
            vo.setScoreLevelBarMaleImg(ChartImgUtil.createBarChart("综合评分情况(男生)", categories, maleValues));
            vo.setScoreLevelBarFemaleImg(ChartImgUtil.createBarChart("综合评分情况(女生)", categories, femaleValues));
        } catch (Exception e) {
            vo.setScoreLevelBarAllImg(null);
            vo.setScoreLevelBarMaleImg(null);
            vo.setScoreLevelBarFemaleImg(null);
        }
        // 生成饼图（JFreeChart）
        String[] genderNames = {"男", "女"};
        double[] genderValues = {maleCount, femaleCount};
        try {
            vo.setGenderPieChartImg(ChartImgUtil.createPieChart("男女比例", genderNames, genderValues));
        } catch (Exception e) { vo.setGenderPieChartImg(null); }
        // 生成柱状图（JFreeChart）
        String[] barCategories = {"全部完成", "未全部完成", "未测试"};
        int[] allBarValues = {allCompletedCount, notAllCompletedCount, notTestedCount};
        int[] maleBarValues = {maleAllCompletedCount, maleNotAllCompletedCount, maleNotTestedCount};
        int[] femaleBarValues = {femaleAllCompletedCount, femaleNotAllCompletedCount, femaleNotTestedCount};
        try {
            vo.setBarChartAllImg(ChartImgUtil.createBarChart("测试完成情况(全体)", barCategories, allBarValues));
        } catch (Exception e) { vo.setBarChartAllImg(null); }
        try {
            vo.setBarChartMaleImg(ChartImgUtil.createBarChart("测试完成情况(男生)", barCategories, maleBarValues));
        } catch (Exception e) { vo.setBarChartMaleImg(null); }
        try {
            vo.setBarChartFemaleImg(ChartImgUtil.createBarChart("测试完成情况(女生)", barCategories, femaleBarValues));
        } catch (Exception e) { vo.setBarChartFemaleImg(null); }
        // 收集班级id到班级名称的映射
        Map<String, String> classIdNameMap = new HashMap<>();
        for (User u : allUsers) {
            if (!"0".equals(u.getType())) continue;
            if (u.getClassId() != null && u.getClassName() != null) {
                classIdNameMap.put(u.getClassId().toString(), u.getClassName());
            }
        }
        // 班级成绩统计表格
        Map<String, Map<String, List<User>>> classGenderUserMap = new HashMap<>();
        for (User u : allUsers) {
            if (!"0".equals(u.getType())) continue;
            String classId = u.getClassId() != null ? u.getClassId().toString() : "";
            String gender = "1".equals(u.getGender()) ? "男" : ("2".equals(u.getGender()) ? "女" : "未知");
            classGenderUserMap.computeIfAbsent(classId, k -> new HashMap<>())
                .computeIfAbsent(gender, k -> new ArrayList<>()).add(u);
            classGenderUserMap.get(classId).computeIfAbsent("合计", k -> new ArrayList<>()).add(u);
        }
        List<List<String>> classStatTableRows = new ArrayList<>();
        classStatTableRows.add(Arrays.asList("班级", "性别", "测试人数", "优秀人数", "良好人数", "及格人数", "不及格人数"));
        List<String> sortedClassIds = new ArrayList<>(classGenderUserMap.keySet());
        Collections.sort(sortedClassIds);
        for (String classId : sortedClassIds) {
            String className = classIdNameMap.getOrDefault(classId, classId);
            Map<String, List<User>> genderMap = classGenderUserMap.get(classId);
            for (String gender : Arrays.asList("男", "女", "合计")) {
                List<User> users = genderMap.getOrDefault(gender, Collections.emptyList());
                int testCount = 0, classExcellent = 0, classFine = 0, classPass = 0, classFail = 0;
                for (User u : users) {
                    List<Score> scores = studentScoreMap.getOrDefault(u.getId(), Collections.emptyList());
                    if (scores.isEmpty()) continue;
                    testCount++;
                    Map<String, Double> projectScoreMap = new HashMap<>();
                    for (ProjectTypeEnum p : allProjects) {
                        double max = 0;
                        for (Score s : scores) {
                            if (p.getCode().equals(s.getProjectType()) && s.getSingleScore() != null) {
                                max = Math.max(max, s.getSingleScore());
                            }
                        }
                        projectScoreMap.put(p.getCode(), max);
                    }
                    double sum = 0;
                    int count = 0;
                    for (ProjectTypeEnum p : allProjects) {
                        sum += projectScoreMap.getOrDefault(p.getCode(), 0.0);
                        count++;
                    }
                    double avg = count > 0 ? sum / count : 0;
                    if (avg < 60) classFail++;
                    else if (avg < 80) classPass++;
                    else if (avg < 90) classFine++;
                    else classExcellent++;
                }
                classStatTableRows.add(Arrays.asList(
                    className, gender, String.valueOf(testCount),
                    percent(classExcellent, testCount), percent(classFine, testCount), percent(classPass, testCount), percent(classFail, testCount)
                ));
            }
        }
        vo.setClassStatTableRows(classStatTableRows);
        // 年级成绩统计表格
        Map<String, String> gradeIdNameMap = new HashMap<>();
        for (User u : allUsers) {
            if (!"0".equals(u.getType())) continue;
            if (u.getGrade() != null && u.getGradename() != null) {
                gradeIdNameMap.put(u.getGrade(), u.getGradename());
            }
        }
        Map<String, Map<String, List<User>>> gradeGenderUserMap = new HashMap<>();
        for (User u : allUsers) {
            if (!"0".equals(u.getType())) continue;
            String grade = u.getGrade();
            String gender = "1".equals(u.getGender()) ? "男" : ("2".equals(u.getGender()) ? "女" : "未知");
            gradeGenderUserMap.computeIfAbsent(grade, k -> new HashMap<>())
                .computeIfAbsent(gender, k -> new ArrayList<>()).add(u);
            gradeGenderUserMap.get(grade).computeIfAbsent("合计", k -> new ArrayList<>()).add(u);
        }
        List<List<String>> gradeStatTableRows = new ArrayList<>();
        gradeStatTableRows.add(Arrays.asList("年级", "性别", "测试人数", "优秀人数", "良好人数", "及格人数", "不及格人数"));
        List<String> sortedGrades = new ArrayList<>(gradeGenderUserMap.keySet());
        Collections.sort(sortedGrades);
        for (String grade : sortedGrades) {
            String gradeName = gradeIdNameMap.getOrDefault(grade, grade);
            Map<String, List<User>> genderMap = gradeGenderUserMap.get(grade);
            for (String gender : Arrays.asList("男", "女", "合计")) {
                List<User> users = genderMap.getOrDefault(gender, Collections.emptyList());
                int testCount = 0, gradeExcellent = 0, gradeFine = 0, gradePass = 0, gradeFail = 0;
                for (User u : users) {
                    List<Score> scores = studentScoreMap.getOrDefault(u.getId(), Collections.emptyList());
                    if (scores.isEmpty()) continue;
                    testCount++;
                    Map<String, Double> projectScoreMap = new HashMap<>();
                    for (ProjectTypeEnum p : allProjects) {
                        double max = 0;
                        for (Score s : scores) {
                            if (p.getCode().equals(s.getProjectType()) && s.getSingleScore() != null) {
                                max = Math.max(max, s.getSingleScore());
                            }
                        }
                        projectScoreMap.put(p.getCode(), max);
                    }
                    double sum = 0;
                    int count = 0;
                    for (ProjectTypeEnum p : allProjects) {
                        sum += projectScoreMap.getOrDefault(p.getCode(), 0.0);
                        count++;
                    }
                    double avg = count > 0 ? sum / count : 0;
                    if (avg < 60) gradeFail++;
                    else if (avg < 80) gradePass++;
                    else if (avg < 90) gradeFine++;
                    else gradeExcellent++;
                }
                gradeStatTableRows.add(Arrays.asList(
                    gradeName, gender, String.valueOf(testCount),
                    percent(gradeExcellent, testCount), percent(gradeFine, testCount), percent(gradePass, testCount), percent(gradeFail, testCount)
                ));
            }
        }
        vo.setGradeStatTableRows(gradeStatTableRows);
        // 统计每个年级的优秀、良好、及格、不及格人数，生成折线图
        List<String> gradeNames = new ArrayList<>();
        List<Integer> excellentList = new ArrayList<>();
        List<Integer> fineList = new ArrayList<>();
        List<Integer> passList = new ArrayList<>();
        List<Integer> failList = new ArrayList<>();
        for (String grade : sortedGrades) {
            String gradeName = gradeIdNameMap.getOrDefault(grade, grade);
            Map<String, List<User>> genderMap = gradeGenderUserMap.get(grade);
            int gradeExcellent = 0, gradeFine = 0, gradePass = 0, gradeFail = 0;
            List<User> users = genderMap.getOrDefault("合计", Collections.emptyList());
            for (User u : users) {
                List<Score> scores = studentScoreMap.getOrDefault(u.getId(), Collections.emptyList());
                if (scores.isEmpty()) continue;
                Map<String, Double> projectScoreMap = new HashMap<>();
                for (ProjectTypeEnum p : allProjects) {
                    double max = 0;
                    for (Score s : scores) {
                        if (p.getCode().equals(s.getProjectType()) && s.getSingleScore() != null) {
                            max = Math.max(max, s.getSingleScore());
                        }
                    }
                    projectScoreMap.put(p.getCode(), max);
                }
                double sum = 0;
                int count = 0;
                for (ProjectTypeEnum p : allProjects) {
                    sum += projectScoreMap.getOrDefault(p.getCode(), 0.0);
                    count++;
                }
                double avg = count > 0 ? sum / count : 0;
                if (avg < 60) gradeFail++;
                else if (avg < 80) gradePass++;
                else if (avg < 90) gradeFine++;
                else gradeExcellent++;
            }
            gradeNames.add(gradeName);
            excellentList.add(gradeExcellent);
            fineList.add(gradeFine);
            passList.add(gradePass);
            failList.add(gradeFail);
        }
        try {
            vo.setGradeLevelLineImg(
                ChartImgUtil.createGradeLevelLineChart(
                    "各年级各等级人数变化情况",
                    gradeNames,
                    excellentList,
                    fineList,
                    passList,
                    failList
                )
            );
        } catch (Exception e) {
            vo.setGradeLevelLineImg(null);
        }
        return vo;
    }

    private static String percent(int n, int total) {
        return total == 0 ? "0(0.00%)" : n + "(" + String.format("%.2f", n * 100.0 / total) + "%)";
    }
}
